Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ZCPU Automated Testing console command & framework #18

Merged
merged 22 commits into from
Jan 25, 2024

Conversation

DerelictDrone
Copy link
Member

@DerelictDrone DerelictDrone commented Nov 15, 2023

Creates basic framework & concmd that runs an entire list of lua files from a folder (lua/wire/zvm/tests), has options for creating & simulating a virtual IOBus device and/or a simple virtual readable/writable membus, plus 5 7 9 10 example tests at the moment

Video of command output

(only live updates like this if pause is disabled, like in mp)

16.compiles.10.tests.mp4

Command output and usage:

image

See wire/zvm/tests/example.lua for a simple example of constructing your own tests (Increments R0 in 4096 steps and checks if it's equal to 4096, else it will fail)

This should be expanded later, more tests, smarter tests, I don't know how many of these test cases I can write because I might disappear soon, but hopefully this test framework is enough that the community can get in on it too

@DerelictDrone
Copy link
Member Author

DerelictDrone commented Nov 16, 2023

@Vurv78 & @thegrb93 I'm gonna take a break from editing this further, should be ready for a look now, when we're close to a merge I'm going to squash it

@Vurv78
Copy link
Contributor

Vurv78 commented Nov 16, 2023

Don't squash, it'll be squashed when merged

@DerelictDrone
Copy link
Member Author

Oh, did you manage to get an auto-squash in? If so, cool!

@Vurv78
Copy link
Contributor

Vurv78 commented Nov 16, 2023

Oh, did you manage to get an auto-squash in? If so, cool!

Nah it's just that default wasn't set to squash and I guess sparky forgot to squash

Copy link
Contributor

@Vurv78 Vurv78 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skimmed very quickly. Need to look into it further and actually test it later on. Just found this. Initial thoughts:

  1. Shouldn't print successful tests, just print Passed/Failed. E2 test suite is currently at 100 tests (and those consist of smaller tests already). Would spam console a lot as the test suite inevitably got larger.

  2. On the same note, would be nice if you could suppress that hl-zasm output print

  3. I think testing at the entity level is not necessary might be a nightmare to manage. Couldn't you just have an instance manually created and track the addresses it writes to instead?

lua/wire/zvm/zvm_tests.lua Outdated Show resolved Hide resolved
@DerelictDrone
Copy link
Member Author

Skimmed very quickly. Need to look into it further and actually test it later on. Just found this. Initial thoughts:

1. Shouldn't print successful tests, just print Passed/Failed. E2 test suite is currently at 100 tests (and those consist of smaller tests already). Would spam console a lot as the test suite inevitably got larger.

2. On the same note, would be nice if you could suppress that hl-zasm output print

3. I think testing at the entity level is not necessary might be a nightmare to manage. Couldn't you just have an instance manually created and track the addresses it writes to instead?

I can do 1 and 2 easily(sometime in the morning), but I'm a bit confused on what you mean by "testing at the entity level"

@Vurv78
Copy link
Contributor

Vurv78 commented Nov 16, 2023

Skimmed very quickly. Need to look into it further and actually test it later on. Just found this. Initial thoughts:

1. Shouldn't print successful tests, just print Passed/Failed. E2 test suite is currently at 100 tests (and those consist of smaller tests already). Would spam console a lot as the test suite inevitably got larger.

2. On the same note, would be nice if you could suppress that hl-zasm output print

3. I think testing at the entity level is not necessary might be a nightmare to manage. Couldn't you just have an instance manually created and track the addresses it writes to instead?

I can do 1 and 2 easily(sometime in the morning), but I'm a bit confused on what you mean by "testing at the entity level"

Might've misread the code, I just saw the Wire_TriggerOutput in your code which likely isn't meant to be there and thought of it being entity code

@DerelictDrone
Copy link
Member Author

Well, it was copied from the entity implementation, but it seems like I forgot to comment one out, I'll get to it in the morning

@DerelictDrone DerelictDrone requested a review from Vurv78 November 16, 2023 18:56
@DerelictDrone
Copy link
Member Author

DerelictDrone commented Nov 16, 2023

This is what the new, shortened output looks like
image

@DerelictDrone
Copy link
Member Author

DerelictDrone commented Nov 26, 2023

Now working from the dedicated server console as well as the in-game one, compilation steps were uncoupled from the CPULib compile function and now takes a fraction of the previous time due to running until compile is completed instead of limiting to 15360 compiler steps per second
image

@DerelictDrone
Copy link
Member Author

@Vurv78 Does the code here meet your expectations or is there anything else you think I should work on before it's merge-worthy?

Copy link
Contributor

@Vurv78 Vurv78 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we want zcpu on workshop, probably want to move the tests directory to the top level, so it doesn't bog down the download.

That or include it in the ignored files in addon.json

lua/wire/cpulib.lua Outdated Show resolved Hide resolved
lua/wire/zvm/tests/execute_from_iobus.lua Outdated Show resolved Hide resolved
lua/wire/zvm/tests/file_example.lua Outdated Show resolved Hide resolved
lua/wire/zvm/zvm_tests.lua Outdated Show resolved Hide resolved
lua/wire/zvm/zvm_tests.lua Outdated Show resolved Hide resolved
ZVMTestSuite.Warnings = 0
ZVMTestSuite.TestFiles = {}
for filename in string.gmatch(names, '[^,]+') do
local files = file.Find('lua/'..testDirectory..'/'..filename..'.lua',"GAME")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would there ever be multiple files returned here? Simplify this to just file.Read?

Copy link
Member Author

@DerelictDrone DerelictDrone Dec 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I split on commas, so you can type test1,test2 to run test1.lua and test2.lua
file.Find seems to allow * which I don't strip from the string, so you can type *example* to run every test with the word example in it (file_example.lua, example.lua)

So you can debug with a few tests at a time rather than all of them at once if you specify the names in a comma delimited list.

lua/wire/zvm/zvm_tests.lua Outdated Show resolved Hide resolved
finalFail = fail
end
if ZVMTestSuite.CurrentWarnings > 0 then
print("Compiler Warnings from "..ZVMTestSuite.TestQueue[#ZVMTestSuite.TestQueue]..": "..ZVMTestSuite.CurrentWarnings)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think showing warnings is necessary.

Copy link
Member Author

@DerelictDrone DerelictDrone Dec 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My intent was that they can be used to try and catch regressions in compilation & syntax, if we checked a prior commit for how many warns were produced vs now we can try to track down code changes that cause more warnings to determine if the warnings are intentional or not.

Though this will only display the NUMBER of warnings the test had, and not any of the actual warning text itself for the moment.

Right now all the warnings that are in during the state of this PR include:
"Trying to generate invalid code"
"Instruction "(opcode name)" is obsolete/an old mnemonic"
"Using complex expression as operand: might corrupt user register"
"Warning: using an unpreserved register"
"Defining a vector inside a function block might cause issues"

Which, I agree that a lot of these aren't exactly necessary to test for, but "Trying to generate invalid code" indicates labels and registers not being used in expressions, which could occur if a user expression exits before all of the values are used, ZAP R0 R1 not parsing R1 for example should generate this warning.

lua/wire/zvm/zvm_tests.lua Outdated Show resolved Hide resolved
lua/wire/zvm/zvm_tests.lua Outdated Show resolved Hide resolved
@Vurv78
Copy link
Contributor

Vurv78 commented Dec 12, 2023

I know I'm very late to review this, but I don't like how much this test framework uses globals and relies on you creating and using a lot of functions you could easily mess up.

I would just want to keep it as simple as possible, zero globals used here:

-- Either have a comment annotation here or have it as a field in the returned table for whether the test *should* fail
local Test = {}

function Test.Setup()
    -- some initial stuff passed here, optional function
end

function Test.Teardown()
    -- also optional
end

function Test.Run(cpu, pass, fail)
    -- do asynchronous stuff and run pass() fail() function callbacks given
end

return Test

If you're going to be the only one working on it, then it's fine as long as you can work with it, but for me, this is just way too complex: I'd probably have a more fleshed out example if I had more knowledge on the cpu. This is a test suite after all and users wouldn't have to interact with it, but if it isn't easy to add tests then it might discourage future devs to create proper tests

@DerelictDrone
Copy link
Member Author

DerelictDrone commented Dec 12, 2023

If we want zcpu on workshop, probably want to move the tests directory to the top level, so it doesn't bog down the download.

That or include it in the ignored files in addon.json

I'm cool with just putting it in the ignored of the addon.json for now, if you know how to include it from cpu_load.lua in the autorun folder or know another way to force it to be included for those who have it when it's in the base dir let me know and I can do that instead.

@DerelictDrone
Copy link
Member Author

DerelictDrone commented Dec 12, 2023

I know I'm very late to review this, but I don't like how much this test framework uses globals and relies on you creating and using a lot of functions you could easily mess up.

The test framework can, and probably should go through some revision.

The initial structure this took was because we needed to wait for compile to complete since it was async(ran on a timer), since the compile is now synchronous(the one exposed by ZVMTestSuite) I could simplify it to just return the compiled code(a table of indexed numbers), so we can do away with the RunTest/RunCPU crap.

-- Either have a comment annotation here or have it as a field in the returned table for whether the test *should* fail

I'll probably make intentional fail tracked in the returned local test's table, so we can just check if it "ExpectsFail" and track those as a separate number from the passed tests when counting it on fail.

function Test.Teardown()
   -- also optional
end

If this is intended to be a "cleanup" function, I actually can't imagine what we'd need to cleanup, right now we're creating a fresh VM for every test, not passing the same one around.

I could make one that calls(if it exists) after pass or fail(just "if finished" basically) and call it something like "Test.OnFinish(bool fail)", if that's your intent.

If you're going to be the only one working on it, then it's fine as long as you can work with it, but for me, this is just way too complex: I'd probably have a more fleshed out example if I had more knowledge on the cpu. This is a test suite after all and users wouldn't have to interact with it, but if it isn't easy to add tests then it might discourage future devs to create proper tests

I'd prefer it to have the ability to be powerful without having to reinvent the wheel for most cases, but still simple enough for future devs to pick up and understand without having to look into the internals.

I'd initially thought it was intuitive but if you say otherwise I'll try and work with any suggestions you have for correcting it.

Here's an example of a potential revised test, let me know what you think of it, once we've got the "revised" test to an ideal state I can get to work on implementation and updating the tests.

-- a potential revision using example.lua as a base.

local Test = {}

function Test.Setup(CPU, TestSuite)
end

function Test.OnFinish(fail, CPU, TestSuite)
end

function Test.CompileError(msg,failCB)
end
-- ^ This one isn't used by the framework, it's a user func that's passed to
-- TestSuite:Deploy to call on a fail right now, but we could switch the func to return bool
-- on pass or fail instead to allow user to do
-- if TestSuite:Deploy() then
-- work
-- else
-- fail
-- end
-- Whichever way is deemed more intuitive, whether true on success or true on error

function Test.Run(CPU, TestSuite, passCB, failCB)
       -- Simpler function to compile and upload code to a device
       -- Compile can still exist, if the test creator needs to read or modify the results.
        TestSuite:Deploy(CPU,"x: INC R0 JMP x",Test.CompileError)
	CPU.Clk = 1
	for i = 0, 4096 do
		CPU:RunStep()
	end
	if CPU.R0 == 4096 then
                -- The CPU ran 4096 blocks that were just adding 1 to R0
                -- so it should be 4096.
		return passCB()
	else
		TestSuite.Error("R0 is not 4096! R0 is " .. tostring(CPUTest.VM.R0))
                -- Can make the failCB take similar args to Error instead.
                return failCB()
	end
end


return CPUTest

@DerelictDrone DerelictDrone requested a review from Vurv78 December 12, 2023 12:09
@thegrb93
Copy link
Contributor

thegrb93 commented Jan 1, 2024

@Vurv78

@Vurv78
Copy link
Contributor

Vurv78 commented Jan 2, 2024

function Test.Teardown()
   -- also optional
end

If this is intended to be a "cleanup" function, I actually can't imagine what we'd need to cleanup, right now we're creating a fresh VM for every test, not passing the same one around.

I could make one that calls(if it exists) after pass or fail(just "if finished" basically) and call it something like "Test.OnFinish(bool fail)", if that's your intent.

No, I just wanted the simplest api possible, if it's not necessary, great, no need to implement it

Here's an example of a potential revised test, let me know what you think of it, once we've got the "revised" test to an ideal state I can get to work on implementation and updating the tests.

-- a potential revision using example.lua as a base.

local Test = {}

function Test.Setup(CPU, TestSuite)
end

function Test.OnFinish(fail, CPU, TestSuite)
end

function Test.CompileError(msg,failCB)
end
-- ^ This one isn't used by the framework, it's a user func that's passed to
-- TestSuite:Deploy to call on a fail right now, but we could switch the func to return bool
-- on pass or fail instead to allow user to do
-- if TestSuite:Deploy() then
-- work
-- else
-- fail
-- end
-- Whichever way is deemed more intuitive, whether true on success or true on error

function Test.Run(CPU, TestSuite, passCB, failCB)
       -- Simpler function to compile and upload code to a device
       -- Compile can still exist, if the test creator needs to read or modify the results.
        TestSuite:Deploy(CPU,"x: INC R0 JMP x",Test.CompileError)
	CPU.Clk = 1
	for i = 0, 4096 do
		CPU:RunStep()
	end
	if CPU.R0 == 4096 then
                -- The CPU ran 4096 blocks that were just adding 1 to R0
                -- so it should be 4096.
		return passCB()
	else
		TestSuite.Error("R0 is not 4096! R0 is " .. tostring(CPUTest.VM.R0))
                -- Can make the failCB take similar args to Error instead.
                return failCB()
	end
end


return CPUTest

Since you said something like Teardown isn't needed, can remove that part.
For the failCB and passCB part I think I'd prefer just traditional asserts / error handling that the test suite pcalls, if you said that none of this is async anymore. If tests may still be async, maybe it's okay to leave like so. But simplifying that if else statement into just assert(CPU.R0 == 4096) would be nice.

Third option could be Test.RunAsync which has current behavior vs Test.Run which is just a simpler version without callbacks using asserts.

Tests can have a virtual filesystem(still falls back to actually using the folder if you don't use it)

Easier to use "Deploy" function, for compiling and flashing straight to a device

Revises all tests to use assert inside of a pcalled context
@DerelictDrone
Copy link
Member Author

@Vurv78 Got them all to compile and pass with your suggestion.

With assert, we even get the file & line it occurred on, which makes it easier to track down programming errors
image

@DerelictDrone
Copy link
Member Author

DerelictDrone commented Jan 18, 2024

@Vurv78 Just wanted to follow up, was there any further modification necessary to the framework before this has your approval?

Maybe merge master back into this and update the addon.json to ignore the tests folder?

@DerelictDrone DerelictDrone merged commit 2ca85dc into wiremod:master Jan 25, 2024
1 check failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants